import React, { useRef, useEffect, useContext, createContext, useReducer, useMemo, memo, useCallback } from 'react';
import { throttle } from 'lodash-es';

// ==============全局常量 ================== //
const DEFAULT_VID = 'vtable';
const DEFAULT_SELECTION_WIDTH = 48;
const DEFAULT_SCROLLY = 500;
const vidMap = new Map();

// ===============reducer ============== //
const initialState = {
  // 当前的scrollTop
  curScrollTop: 0,
  // 总行数
  totalLen: 0,

  // 当前表格视区宽度
  curTableWith: 0,
  // 当前的scrollLeft
  curScrollLeft: 0,
  // 当前列宽度数组
  colWidths: [],
  // 最大固定列的索引
  maxFixedLeftIndex: 0,
};

function reducer(state: any, action: any) {
  const { curScrollTop = state.curScrollTop, curScrollLeft = state.curScrollLeft, totalLen } = action;

  switch (action.type) {
    // 改变trs 即 改变渲染的列表trs
    case 'changeTrs':
      return {
        ...state,
        curScrollTop,
        curScrollLeft,
      };
    // 更改totalLen
    case 'changeTotalLen':
      return {
        ...state,
        totalLen,
        curScrollTop: totalLen === 0 ? 0 : curScrollTop,
      };

    default:
      throw new Error();
  }
}

interface IComputedColSpanParams {
  curTableWith: number;
  curScrollLeft: number;
  colWidths: number[];
  maxFixedLeftIndex: number;
}

function computedPrevNextColSpan({
  curTableWith,
  curScrollLeft,
  colWidths,
  maxFixedLeftIndex = 0,
}: IComputedColSpanParams) {
  // var curTableWith = 100; // mock
  // var curScrollLeft = 148; // mock
  // var colWidths = [48, 100, 200, 200, 200, 200]; // from columns
  // var maxFixedLeftIndex = 1;

  let prevHolderColIndex = 0;
  let nextHolderColIndex = 0;

  let prevBufferIndex = 0;
  let nextBufferIndex;

  let viewStartColIndex;
  let viewEndColIndex;

  let sum = 0;
  for (let i = 0; i < colWidths.length; i++) {
    sum += colWidths[i];
    if (sum >= curScrollLeft && viewStartColIndex === undefined) {
      viewStartColIndex = i;
      prevBufferIndex = i - 1;
      prevHolderColIndex = i - 2;
    }
    if (sum >= curScrollLeft + curTableWith && viewEndColIndex === undefined) {
      viewEndColIndex = i;
      nextBufferIndex = i + 1;
      nextHolderColIndex = i + 2;
      break;
    }
  }

  const prevHolderColSpan = Math.max(prevHolderColIndex - maxFixedLeftIndex, 0);
  const nextHolderColSpan = nextHolderColIndex && Math.max(colWidths.length - nextHolderColIndex, 0);
  return [
    prevHolderColSpan,
    nextHolderColSpan || 0,
    Math.max(maxFixedLeftIndex + 1, prevBufferIndex, 0),
    nextBufferIndex || colWidths.length,
  ];
}

function computeNextChildren(params: IComputedColSpanParams, children: any[]) {
  const { maxFixedLeftIndex = 0 } = params;
  const [prevHolderColSpan, nextHolderColSpan, prevBufferIndex, nextBufferIndex] = computedPrevNextColSpan(params);
  const nextChildren = [
    ...children.slice(0, maxFixedLeftIndex + 1),
    prevHolderColSpan && <td colSpan={prevHolderColSpan} key="prevHolderTd" />,
    ...children.slice(prevBufferIndex, nextBufferIndex + 1),
    nextHolderColSpan && <td colSpan={nextHolderColSpan} key="nextHolderTd" />,
  ].filter(Boolean);
  return nextChildren;
}

// ===============context ============== //
const ScrollContext = createContext({
  dispatch: (_arg: any) => undefined as any,
  renderLen: 0,
  totalLen: 0,
  start: 0,
  // =============
  vid: DEFAULT_VID,

  curTableWith: initialState.curTableWith,
  curScrollLeft: initialState.curScrollLeft,
  colWidths: initialState.colWidths,
  maxFixedLeftIndex: initialState.maxFixedLeftIndex,
});

function VRow(props: any): JSX.Element {
  const { curTableWith, curScrollLeft, colWidths, maxFixedLeftIndex } = useContext(ScrollContext);

  const { children, style, ...restProps } = props;

  // 计算横向应该显示的 td
  const computedChildren = useMemo(
    () =>
      !Array.isArray(children) || !(curTableWith || colWidths?.length)
        ? children
        : computeNextChildren(
            {
              curTableWith,
              curScrollLeft,
              colWidths,
              maxFixedLeftIndex,
            },
            children,
          ),
    [children, curTableWith, colWidths?.join(','), curScrollLeft, maxFixedLeftIndex],
  );

  window.console.log('===computedChildren==>', computedChildren);

  return <tr {...restProps}>{computedChildren}</tr>;
}

function VWrapper(props: any): JSX.Element {
  const { renderLen, start, dispatch, vid, totalLen } = useContext(ScrollContext);
  const { children, ...restProps } = props;

  const contents = useMemo(() => children[1], [children]);
  const contentsLen = useMemo(() => contents?.length ?? 0, [contents]);

  useEffect(() => {
    if (totalLen !== contentsLen) {
      dispatch({
        type: 'changeTotalLen',
        totalLen: contentsLen,
      });
    }
  }, [contentsLen, dispatch, vid, totalLen]);

  // 计算纵向应该显示的 tr
  const computedChildren = useMemo(() => {
    let tempNode: any = null;
    if (Array.isArray(contents) && contents.length) {
      tempNode = [
        children[0],
        contents.slice(start, start + (renderLen ?? 1)).map(item => {
          if (Array.isArray(item)) {
            // 兼容antd v4.3.5 --- rc-table 7.8.1及以下
            return item[0];
          }
          // 处理antd ^v4.4.0  --- rc-table ^7.8.2
          return item;
        }),
      ];
    } else {
      tempNode = children;
    }
    return tempNode;
  }, [children, contents, start, renderLen]);

  return <tbody {...restProps}>{computedChildren}</tbody>;
}

function VTable(props: any): JSX.Element {
  const { style, children, otherParams, ...rest } = props;

  const { vid, scrollY, rowHeight, onScroll, curTableWith, colWidths, maxFixedLeftIndex } = otherParams ?? {};

  const [state, dispatch] = useReducer(reducer, initialState);

  const refContent = useRef<HTMLDivElement>(null);

  // table总高度
  const tableHeight = useMemo<number>(() => {
    return rowHeight * state.totalLen;
  }, [rowHeight, state.totalLen]);

  // 渲染的条数
  const renderLen = useMemo<number>(() => {
    return !state.totalLen || scrollY <= 0 ? 0 : Math.ceil(scrollY / rowHeight) + 1;
  }, [rowHeight, state.totalLen, scrollY]);

  // 渲染中的第一条
  const start = rowHeight ? Math.floor(state.curScrollTop / rowHeight) : 0;

  // 偏移量
  const offsetStart = rowHeight ? state.curScrollTop % rowHeight : 0;

  const refState = useRef(state);
  refState.current = state;
  // 绑定 potooTableBody 横向滚动
  useEffect(() => {
    const throttleScroll = throttle(
      e => {
        const scrollLeft: number = e?.target?.scrollLeft ?? 0;
        onScroll?.({ scrollLeft, scrollTop: refState.current.curScrollTop });
        dispatch({
          type: 'changeTrs',
          curScrollLeft: scrollLeft,
        });
        refScrollRight.current?.parentElement?.setAttribute('data-lastScrollX', String(scrollLeft));
      },
      60,
      { leading: true },
    );

    const ref = refContent?.current?.parentNode as HTMLElement;

    ref?.addEventListener('scroll', throttleScroll);
    return () => {
      ref?.removeEventListener('scroll', throttleScroll);
    };
  }, [onScroll, renderLen, vid]);

  // 绑定 滚动条 竖向滚动
  const refScrollRight = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const throttleScroll = throttle(
      e => {
        console.time('scrollYYY');
        const scrollTop: number = e?.target?.scrollTop ?? 0;
        onScroll?.({ scrollTop, scrollLeft: refState.current.curScrollLeft });
        dispatch({
          type: 'changeTrs',
          curScrollTop: renderLen <= refState.current.totalLen ? scrollTop : 0,
        });
        refScrollRight.current?.parentElement?.setAttribute('data-lastScrollY', String(scrollTop));
        console.timeEnd('scrollYYY');
      },
      60,
      { leading: true },
    );

    refScrollRight.current?.addEventListener('scroll', throttleScroll);
    return () => {
      refScrollRight.current?.removeEventListener('scroll', throttleScroll);
    };
  }, [onScroll, renderLen, vid]);

  // 桥接触摸板滑动事件
  const refWheelRaf = useRef<number>();
  useEffect(() => {
    const handleWheel = (event: any) => {
      const { deltaX, deltaY } = event;
      event.preventDefault();
      event.stopPropagation();
      refWheelRaf.current = requestAnimationFrame(() => {
        refScrollRight.current?.scrollBy(0, deltaY);
        refScrollRight.current?.parentElement?.scrollBy(deltaX, 0);
      });
    };
    refContent.current?.addEventListener('mousewheel', handleWheel, { passive: false });
    return () => {
      refContent.current?.removeEventListener('mousewheel', handleWheel);
      if (refWheelRaf.current) {
        cancelAnimationFrame(refWheelRaf.current);
        refWheelRaf.current = undefined;
      }
    };
  }, [onScroll, vid]);

  // 便于操作scrollTop
  useEffect(() => {
    vidMap.set(vid, { colWidths, rowHeight, refScrollRight });

    // resize后保留上次滚动位置
    const lastScrollLeft = refScrollRight.current?.parentElement?.getAttribute('data-lastScrollX');
    const lastScrollTop = refScrollRight.current?.parentElement?.getAttribute('data-lastScrollY');
    if (lastScrollTop) {
      requestAnimationFrame(() => {
        refScrollRight.current?.scrollTo(0, Number(lastScrollTop));
      })
    }
    dispatch({
      type: 'changeTrs',
      curScrollLeft: Number(lastScrollLeft) || 0,
      curScrollTop: Number(lastScrollTop) || 0,
    });
    return () => {
      vidMap.delete(vid);
    };
  }, [vid, colWidths?.join(','), rowHeight]);

  return (
    <>
      <div
        ref={refContent}
        className="contentBox"
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          bottom: 0,
        }}>
        <ScrollContext.Provider
          value={{
            dispatch,
            start,
            renderLen,
            totalLen: state.totalLen,
            vid,

            curTableWith,
            curScrollLeft: state.curScrollLeft,
            colWidths,
            maxFixedLeftIndex,
          }}>
          <table
            {...rest}
            style={{
              ...style,
              position: 'relative',
              transform: `translateY(-${offsetStart}px)`,
            }}>
            {children}
          </table>
        </ScrollContext.Provider>
      </div>
      {/* 用于制作右侧的纵向滚动条，横向滚动直接用 potooTableBody 的 */}
      <div
        ref={refScrollRight}
        className="scrollBarRight"
        style={{
          position: 'sticky',
          top: 0,
          left: 0,
          bottom: 0,
          width: 15,
          height: scrollY,
          boxSizing: 'border-box',
          overflowY: 'auto',
          transform: `translateX(${curTableWith - 15}px)`,
        }}>
        <div style={{ width: 15, height: tableHeight }}></div>
      </div>
    </>
  );
}

// ================导出===================
export function VList(props: {
  scrollY: number;
  rowHeight?: number;
  // 到底的回调函数
  onScroll?: (params: { scrollTop: number; scrollLeft: number }) => void;
  // 唯一标识
  vid?: string;

  // 横向所需参数
  scrollX?: number;
  colWidths?: number[];
  defaultSelectionWidth?: number;
}): any {
  const {
    vid = DEFAULT_VID,
    scrollY = DEFAULT_SCROLLY,
    rowHeight = DEFAULT_SELECTION_WIDTH,
    onScroll: _onScroll,

    scrollX: curTableWith,
    colWidths: _colWidths,
    defaultSelectionWidth = DEFAULT_SELECTION_WIDTH,
  } = props;

  return {
    table: memo((p: any) => {
      const { columns, data } = p.children?.[1]?.props ?? {};

      // 固定的列最大的索引，便于计算横向
      const maxFixedLeftIndex = columns?.findLastIndex((v: any) => v.fixed === 'left') ?? 0;

      // 默认勾选列宽度48，其他列全部有width才开启横向虚拟滚动
      const buzColsWidths = columns
        ?.filter((v: any) => !v.className?.includes('-selection-column'))
        .map((v: any) => v.width);
      const colWidths =
        _colWidths || buzColsWidths.every((v: any) => Boolean(v))
          ? [defaultSelectionWidth].concat(buzColsWidths)
          : undefined;

      const refOnScroll = useRef(_onScroll);
      refOnScroll.current = _onScroll;
      const onScroll = useCallback(arg => refOnScroll.current?.(arg), []);

      return useMemo(
        () => (
          <VTable
            {...p}
            otherParams={{
              vid,
              scrollY,
              rowHeight,
              onScroll,
              curTableWith,
              colWidths,
              maxFixedLeftIndex,
            }}
          />
        ),
        [vid, scrollY, rowHeight, curTableWith, colWidths?.join(','), maxFixedLeftIndex, columns, data],
      );
    }),
    body: {
      wrapper: VWrapper,
      row: VRow,
    },
  };
}

export function scrollTo(
  option: {
    // 行
    row?: number;
    y?: number;
    // 列
    col?: number;
    x?: number;
    // 标识
    vid?: string;
  } = {},
) {
  const { row, y, col, x, vid = DEFAULT_VID } = option;

  try {
    const { refScrollRight, rowHeight, colWidths } = vidMap.get(vid);

    if (refScrollRight.current) {
      const scrollBarNode = refScrollRight.current;
      const tBodyNode = scrollBarNode.parentNode;
      if (row !== undefined) {
        if (row - 1 > 0) {
          scrollBarNode.scrollTop = (row - 1) * (rowHeight ?? 0);
        } else {
          scrollBarNode.scrollTop = 0;
        }
      } else if (y !== undefined) {
        scrollBarNode.scrollTop = y ?? 0;
      }
      if (col !== undefined) {
        if (col - 1 > 0) {
          tBodyNode.scrollLeft = (colWidths as number[])?.slice(0, col).reduce((a, b) => a + b) ?? 0;
        } else {
          tBodyNode.scrollLeft = 0;
        }
      } else if (x !== undefined) {
        tBodyNode.scrollLeft = x ?? 0;
      }
    }

    return { vid, rowHeight };
  } catch (e) {
    console.error('dont call scrollTo before init table');

    return { vid, rowHeight: -1 };
  }
}

// Test
window.__scrollTo = scrollTo;
